/**
 *   处理数据包的封包 和 解包
 *    Pomelo package format:
 * +------+-------------+------------------+
 * | type | body length |       body       |
 * +------+-------------+------------------+
 *
 *
 * Head: 4bytes
 *   0: package type,
 *      1 - handshake,
 *      2 - handshake ack,
 *      3 - heartbeat,
 *      4 - data
 *      5 - kick
 *   1 - 3: big-endian body length
 * Body: body length bytes
 *
 */

(function (exports, ByteArray) {
    let Protocol = exports;
    // 包头长度为4字节
    const PKG_HEAD_BYTES = 4;
    // 1个字节表示 包类型
    const PKG_FLAG_BYTES = 1;
    //消息flag type 长度
    const MSG_FLAG_BYTES = 1;
    // 如果开启了压缩路由 那么只占用2个字节  因为code是 uint16位 用高尾端来排序
    const MSG_ROUTE_CODE_BYTES = 2
    // 没有开启压缩路由 那么用1个字节来存路由字符串的长度
    const MSG_ROUTE_STRING_LEN = 1

    // 开启路由压缩掩码
    const MSG_COMPRESS_ROUTE_MASK = 0x01;
    // 获取消息类型掩码
    const MSG_ROUTE_TYPE_MASK = 0x07;
    // 错误消息掩码
    const MSG_IS_ERROR_MASK = 0x20;


    // 消息封包解包时候使用
    const MSG_HAS_ROUTE = 0x01;
    const MSG_HAS_ID = 0x02;
    const MSG_HAS_MSG = 0x04;


    let Package = Protocol.Package = {};
    let Message = Protocol.Message = {};

    // 握手 （连接后的第一条消息）
    Package.TYPE_HANDSHAKE = 1;
    // 收到服务器的握手信息后 确认
    Package.TYPE_HANDSHAKE_ACK = 2;
    // 心跳包 消息
    Package.TYPE_HEARTBEAT = 3;
    // data 消息
    Package.TYPE_DATA = 4;
    // session kick 消息
    Package.TYPE_KICK = 5;


    Message.TYPE_REQUEST = 0;
    Message.TYPE_NOTIFY = 1;
    Message.TYPE_RESPONSE = 2;
    Message.TYPE_PUSH = 3;


    /**
     * copy数据到buffer中
     * @param buffer
     * @param desIdx 从desIdx开始写
     * @param src 需要写入到buffer的数据
     * @param srcIdx src的偏移量
     * @param dataLen src的长度
     */
    Package.copyArray = function (buffer, desIdx, src, srcIdx, dataLen) {

        // 这里为啥要加一个目标位置的长度呢？ 因为 这样i就不是0了 而是 desIdx 所以dataLen 需要加一个desIdx
        dataLen +=desIdx;
        // 如果是nodejs 环境有Buffer话
        if (typeof src.copy === "function") {
            src.copy(buffer, desIdx, srcIdx, srcIdx + dataLen)
        } else {
            // 使用Uint8Array
            // 遍历 源数据 一个一个拷到目标数据里
            for (let i = desIdx; i < dataLen; i++) {

                buffer[desIdx++] = src[srcIdx++]
            }

        }

    }

    /**
     * pomelo 封包格式  1type+ 3 datalen +data 的封包
     * +------+-------------+------------------+
     * | type | body length |       body       |
     * +------+-------------+------------------+
     * @param type 1-5  Package.TYPE_XXXX
     * @param body  数据
     */
    Package.encode = function (type, body) {

        let dataLen = body ? body.length : 0;

        // uint8Array 是一个TypeArray 可以直接通过下标修改
        let buffer = new ByteArray(PKG_HEAD_BYTES + dataLen);
        let index = 0;
        buffer[index++] = type & 0xff
        buffer[index++] = (dataLen >> 16) & 0xff
        buffer[index++] = (dataLen >> 8) & 0xff
        buffer[index++] = dataLen & 0xff

        if (body) {
            // 将数据拷贝到 buffer内  完成了 1type+ 3 datalen +data 的封包
            Package.copyArray(buffer, index, body, 0, dataLen)
        }
        return buffer;
    }

    /**
     * string 转成utf-8的二进制数据
     *
     * Unicode编码(16进制)　║　UTF-8 字节流(二进制) 　
          000000 - 00007F　║　0xxxxxxx 　(0-127)
          000080 - 0007FF　║　110xxxxx 10xxxxxx 　(128-2047)
          000800 - 00FFFF　║　1110xxxx 10xxxxxx 10xxxxxx (2048-65535)
          010000 - 10FFFF　║　11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 　(65536- 1114111)
     * id message id;
     * route message route
     * msg message body
     * socketio current support string
     * @param data
     * @return Buffer
     */
    Package.strEncode = function (data) {
        //把 4个范围的都处理一次
        let tmpBuf = new ByteArray(data.length * 4);
        let offset = 0;
        let coders = [];
        for (let i = 0; i < data.length; i++) {
            // 该code是utf-16的整数值 0-65535  16位为一个单位，但不代表字符只有16位
            let code = data.charCodeAt(i)
            // 如果是小于128 也就是单字节 ，不需要处理
            if (code <= 0x7F) {
                coders = [code]
            } else if (code <= 0x7FF) {
                //如果是小于等于 2047 那么就是2个字节保存
                // 那么码点是落在0x80-0x7FF之间 使用110xxxxx 10xxxxxx 模板从右往左依次填入
                // 用 高尾端 来存
                // 地址放  charCode>>6 （拿到前5位） 在与一次 0xc0（192  因为 11000000）
                // 第二位放charCode 的后6位 charCode & 00111111(63)
                coders = [0xC0 | (code >> 6), 0x80 | (code & 0x3F)]

            } else if (code <= 0xFFFF) {
                // 用3个字节保存
                // 高尾端 来存
                // 码点落在000800 - 00FFFF 1110xxxx 10xxxxxx 10xxxxxx
                // 最后一位用 0x3f（111111）来获取 最后加上 0x80（128）
                // 倒数第二位 0x80(128) 与上 charCode &  0xfc0(1111 1100 0000)
                // 因为我们要先跳过最后的6位从低7位开始拿6位 来放到模板的第二部分 所以要&FC0 返回这时候code是12位 右移6位 拿到 高位的6位 最后与一次0x80(模板是10xxxxxx 最小就是 10 000000)

                //第一位 直接拿到4位的值 （本来是16位 移动了12位 得到一个4位的二进制 ，之后解出来要 左移12位）
                coders = [0xE0 | (code >> 12), 0x80 | (code & 0xFC0) >> 6, 0x80 | (code & 0x3F)]

            } else {
                //用3字节保存
                // 高尾端来存
                // 码点落在010000 - 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                // 第二部分 ：逻辑和上面的差不多 就是 第二部分使用 0x3f000 来获取后18位，再右移12 拿到高位6位
                // 第三部分 ： 逻辑和之前第二部分一样 使用 0xFC0 来那到后12位 在右移6位 拿到高位6位
                // 最后一部分 就是 直接用 0x3F 来拿最后6位
                coders = [0xF0 | (code >> 18), 0x80 | (code & 0x3f000) >> 12, 0x80 | (code & 0xFC0) >> 6, 0x80 | (code & 0x3F)]
            }
            // 按顺序一个萝卜一个坑来放到uint8array里面
            for (let i = 0; i < coders.length; i++) {

                tmpBuf[offset++] = coders[i]
            }

        }
        // 准备一个新的uint8Array 长度为tmpBuf的字节长度 这个是真实的长度 之前tmpBuf的长度是最大长度实际可能用不到
        let resultBuffer = new ByteArray(offset)
        Package.copyArray(resultBuffer, 0, tmpBuf, 0, offset)
        return resultBuffer
    }

    /**
     *
     * []byte 转回str
     * @param buffer
     * @returns {string}
     */
    Package.strDecode = function (buffer) {

        let codes = []
        let code = 0;
        let offset = 0;
        let end = buffer.length;
        let str = "";
        while (offset < end) {
            // 高尾端原因 如果你第一位的值都是小于 128 那么 肯定是单字节的因为 单字节最多是 01111111（127） 最高位没用
            if (buffer[offset] < 128) {
                code = buffer[offset++];
            } else if (buffer[offset] < 224) {
                // 同理 ，如果第一位是小于224 双字节 高位字节 最多放 11011111 （223） 所以 小于224 那一定是 双字节的
                code = (buffer[offset] & 0x1F) << 6 + (buffer[offset + 1] & 0x3F);
                offset += 2;

            } else if (buffer[offset] < 240) {
                //用00001111来获取最左第1个字节的值 在左移12恢复
                //00111111 来获取最左第2个字节的值 在左移6恢复
                //用00111111 来获取最后一位的值
                // 相加即可
                code = (buffer[offset] & 0x0F) << 12 + (buffer[offset + 1]) & 0x3F << 6 + (buffer[offset + 2] & 0x3F)

                offset += 3;
            } else {
                // 用00000111来取第一位的值
                // 后面和之前的做法是一样的
                code = (buffer[offset] & 0x07) << 18 + (buffer[offset + 1] & 0x3F) << 12 + (buffer[offset + 2]) & 0x3F << 6 + (buffer[offset + 3] & 0x3F)
                offset += 4;
            }
            codes.push(code);
        }
        str = String.fromCharCode.apply(null, codes);

        return str;
    }
    /**
     * pomelo 数据包 解包
     *
     * @param buffer  序列化的二进制数据
     */
    Package.decode = function (buffer) {
        let offset = 0;
        //可能有多条数据
        let result = [];
        let bytes = new ByteArray(buffer)
        let dataLen = 0;
        // length是元素数 为啥不用 byteLength？好像是应该使用length
        while (offset < bytes.length) {
            let type = bytes[offset++];
            // 这里用>>>0 应该是保证是一个有效的 Number数字
            dataLen = bytes[offset++] >> 16 | bytes[offset++] >> 8 | bytes[offset++] >>> 0;
            let body = new ByteArray(dataLen)
            // 从offset位置开始 读 dataLen 长度 把数据写到body里面
            Package.copyArray(body, 0, bytes, offset, dataLen);
            offset += dataLen;
            result.push({type, body});
        }

        return result.length === 1 ? result[0] : result;
    }
    /**
     * 检查消息中是否包含id
     * @param type
     * @returns {boolean}
     */
    Message.msgHasId = function (type) {

        return type === Message.TYPE_REQUEST || type === Message.TYPE_RESPONSE;
    }

    /**
     * 拿到 message id 共占了多少个字节 pitaya 使用 减少 2**7 次方来存
     * @param {Number} id
     * @returns {number}
     * @constructor
     */
    Message.getIdByteLen = function (id) {
        // let len = 0;
        // do {
        //     len++;
        //     id >>>= 7;
        // } while (id > 0);
        // return len;

       return Math.ceil(id.toString(2).length/7)
    }

    /**
     *
     * 检查消息是否包含route
     * @param type 消息type
     */

    Message.msgHasRoute = function (type) {
        return type === Message.TYPE_REQUEST ||
            type === Message.TYPE_PUSH ||
            type === Message.TYPE_NOTIFY;
    }
    /**
     * 检查消息类型是否是无效的
     *
     * @param type
     * @returns {boolean|boolean}
     */
    Message.isInvalidType = function (type) {

        return type !== Message.TYPE_REQUEST &&
            type !== Message.TYPE_NOTIFY &&
            type !== Message.TYPE_RESPONSE &&
            type !== Message.TYPE_PUSH;
    }


    /**
     * 编码 flag 字段
     * @param type
     * @param compressRoute
     * @param buffer
     * @param offset
     * @returns Number  当前偏移量
     */
    Message.encodeMsgFlag = function (type, compressRoute, buffer, offset) {

        if (Message.isInvalidType(type)) {
            throw new Error("位置消息类型：" + type);
        }

        buffer[offset] = (type << 1) | (compressRoute ? MSG_COMPRESS_ROUTE_MASK : 0);

        return offset + PKG_FLAG_BYTES;

    }

    /**
     * 编码  msgId  使用低尾端的方式排序
     * @param id
     * @param buffer
     * @param offset
     * @returns {*}
     */
    Message.encodeMsgId = function (id, buffer, offset) {
        do {
            let r = id % 128;
            let next = Math.floor(id/128);  //相当于 Math.floor(id/128)  id>>7

            if (next !== 0) {
                buffer[offset++] = r + 128
            } else {
                buffer[offset++] = r
            }
            id = next;
        } while (id > 0)
        return offset
    }

    /**
     *
     *
     * @param route 路由buffer 而不是字符串
     * @param buffer
     * @param offset 偏移量
     * @param compressRoute  是否使用了压缩路由
     * @returns {*}
     */
    Message.encodeMsgRoute = function (route, buffer, offset, compressRoute) {

        if (compressRoute) {

            buffer[offset++] = (route >> 8) & 0xFF;

            buffer[offset++] = route & 0xFF;

        } else {

            buffer[offset++] = route.length & 0xff;
            Package.copyArray(buffer, offset, route, 0, route.length);

            offset += route.length;

        }

        return offset;

    }

    /**
     * 编码 消息 body
     * @param buffer
     * @param offset
     * @param body  数据body buffer (不是字符串)
     * @returns {*}
     */
    Message.encodeMsgBody = function (buffer, offset, body) {
        Package.copyArray(buffer, offset, body, 0, body.length);
        offset += body.length;
        return offset;
    }


    /**
     * 消息编码
     *  flag 消息格式 ：
     *  消息类型是  0x01 ~ 0x04 封装到flag上的时候把消息类型 左移了一次 并转成了uint8 单字节
     *  如果有使用压缩理由 那么 与一次  0x01
     *  如果消息是一个错误消息 与一次 0x20 （00100000）相当于后四位留给了 压缩路由和消息类型
     *
     * // ----------------------------------------------------
     * // |   type   |  flag  |       other                  |
     * // |----------|--------|----------------------------- |
     * // | request  |----000-|<message id>|<route><msg body>|
     * // | notify   |----001-|<route> <msg body>            |
     * // | response |----010-|<message id> <msg body>       |
     * // | push     |----011-|<route> <msg body>            |
     * // ----------------------------------------------------
     * @param  {Number} id            message id
     * @param  {Number} type          message type
     * @param  {Number} compressRoute whether compress route
     * @param  {Number|String} route  route code or route string
     * @param  {Buffer} msg           message body bytes
     * @return {Buffer}               encode result
     */
    Message.encode = function (id, type, compressRoute, route, msg) {

        // 用来检查 id ,route  msg 是否存在
        let checkMask = 0x00;
        // 是否是一个需要回调请求 没有id可能 是push 或者 notify
        if (Message.msgHasId(type)) {
            checkMask |= MSG_HAS_ID;
        }
        // 有route的请求类型 包括  request push notify
        if (Message.msgHasRoute(type)) {
            checkMask |= MSG_HAS_ROUTE;
        }

        if (msg) {
            checkMask |= MSG_HAS_MSG;
        }




        //存消息长度
        let msgLen = MSG_FLAG_BYTES;
        // 如果消息包含id 那么 计算id的长度 （字节长度）
        let idLen = (checkMask & MSG_HAS_ID) === MSG_HAS_ID ? Message.getIdByteLen(id) : 0;
        msgLen += idLen;



        let routeBuffer
        // 如果消息是有路由
        if ((checkMask & MSG_HAS_ROUTE) === MSG_HAS_ROUTE) {

            if (compressRoute) {
                if (typeof compressRoute !== "number") {
                    throw new Error("错误的路由数字标志")
                } else {
                    msgLen += MSG_ROUTE_CODE_BYTES;
                }

            } else {

                msgLen += MSG_ROUTE_STRING_LEN;

                routeBuffer = Package.strEncode(route)

                msgLen += routeBuffer.length || 0;

            }
        }



        if ((checkMask & MSG_HAS_MSG) === MSG_HAS_MSG) {
            msgLen += msg.length;
        }



        // 开始封包

        let buffer = new ByteArray(msgLen);
        // 当前偏移量
        let offset = 0;

        // 封flag

        offset = Message.encodeMsgFlag(type, compressRoute, buffer, offset);




        if ((checkMask & MSG_HAS_ID) === MSG_HAS_ID) {
            offset = Message.encodeMsgId(id, buffer, offset);
        }



        //  如果有路由,那就封进buffer
        if ((checkMask & MSG_HAS_ROUTE) === MSG_HAS_ROUTE) {

            offset = Message.encodeMsgRoute(routeBuffer, buffer, offset, compressRoute);

        }

        //  如果有消息body,那就封进buffer
        if ((checkMask & MSG_HAS_MSG) === MSG_HAS_MSG) {
            Message.encodeMsgBody(buffer, offset, msg);
        }

        return buffer;
    }


   /**
    * 消息解码
    * @param buffer
    */
   Message.decode = function (buffer) {
      let offset = 0;
      let flag = buffer[offset++];
      let bufferLen = buffer.length;
      let isCompressRoute = (flag&MSG_COMPRESS_ROUTE_MASK) === MSG_COMPRESS_ROUTE_MASK;
      let type = flag >> 1 & MSG_ROUTE_TYPE_MASK;
      let isErrMsg = flag & MSG_IS_ERROR_MASK;

      let id = 0;
      let route;

      if(Message.msgHasId(type)){
         // 处理id
         for (let i=offset;i<buffer.length;i++){
            let num = buffer[i];
            id+= (num&0x7F) << (7*(i-offset))

            // 说明拿到最后
            if(num <128){
               offset = i;
               break;
            }
         }

      }


      if(Message.msgHasRoute(type)){
         // 是否开路由压缩
         if(isCompressRoute){
            route = parseInt( (buffer[offset++] << 8) + buffer[offset++]);
         }else{
            // 第一位是route长度
           let routeLen = parseInt(buffer[offset++]);
           if(routeLen) {
              let routeBuf = new ByteArray(routeLen);
              Package.copyArray(routeBuf, 0, buffer, offset, routeLen);
              route = Package.strDecode(routeBuf);
              offset += routeLen;
           }else{
              route = "";
           }
         }
      }

      // 剩下的都是消息body
      let dataLen = bufferLen -  offset;

      let dataBuf = new ByteArray(dataLen);

      Package.copyArray(dataBuf,0,buffer,offset,dataLen);

      let data = Package.strDecode(dataBuf);

      let message = {type,id,body:data,route,compressRoute:isCompressRoute,isErr:isErrMsg};

      return message;

   }

    // 挂载到全局
    if (typeof (window) != "undefined") {
        window.Protocol = Protocol;
    }

})(typeof (window) == "undefined" ? module.exports : (this.Protocol = {}), typeof (window) == "undefined" ? Buffer : Uint8Array)
